﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.InteropServices;
using System.Collections;
using System.IO.MemoryMappedFiles;

namespace Liquifier
{
    /// <summary>
    /// The TypeData class is responsible for storing and feeding information about a type to Liquifier instances and other components. It encapsulates 
    /// a commonly used set of properties/characteristics of the type, and allows for caching of this information independant of the position in the 
    /// stack (which will vary enormously for each Liquifier instance...)
    /// </summary>
    public partial class TypeData
    {
        public string AssemblyQualifiedName;

        #region Constructors and Entry Points

        private TypeData(Type targettype)
        {
            AssemblyQualifiedName = targettype.AssemblyQualifiedName;
            _type = targettype;
        }

        private TypeData(string assemblyQualifiedName)
        {
            this.AssemblyQualifiedName = assemblyQualifiedName;
        }

        /* Force access through these so we can cache TypeData; this caching accounts for ~50% of our performance characteristics - it is important */

        public static TypeData GetTypeData(Type type)
        {
            if (!typeDataCache.ContainsKey(type.AssemblyQualifiedName))
            {
                typeDataCache.Add(type.AssemblyQualifiedName, new TypeData(type));
            }
            return typeDataCache[type.AssemblyQualifiedName];
        }

        public static TypeData GetTypeData(string assemblyqualifiedname)
        {
            if (!typeDataCache.ContainsKey(assemblyqualifiedname))
            {
                typeDataCache.Add(assemblyqualifiedname, new TypeData(assemblyqualifiedname));
            }
            return typeDataCache[assemblyqualifiedname];
        }

        private static Dictionary<string, TypeData> typeDataCache = new Dictionary<string, TypeData>();

        #endregion

        #region Member Collection

        public MemberCollection Members;

        public void CollectMembers()
        {
            if (Members == null)
            {
                Members = new MemberCollection();

                foreach (FieldInfo i in GetAllFields(Type))
                {
                    Members.Add(new FieldMember(i));
                }
                foreach (PropertyInfo i in GetAllProperties(Type))
                {
                    Members.Add(new PropertyMember(i));
                }
            }
        }

        /* FlattenHierarchy does not work for private members of superclasses, so we process each base type manually */

        protected static IEnumerable<FieldInfo> GetAllFields(Type t)
        {
            if (t == null)
                return Enumerable.Empty<FieldInfo>();
            return t.GetFields(flags).Concat(GetAllFields(t.BaseType));     //faster than union - duplicates dont matter because they are culled when serialising anyway
        }

        protected static IEnumerable<PropertyInfo> GetAllProperties(Type t)
        {
            if (t == null)
                return Enumerable.Empty<PropertyInfo>();
            return t.GetProperties(flags).Concat(GetAllProperties(t.BaseType));
        }

        protected static BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly;

        #endregion

        #region Native Type

        public Type Type
        {
            get
            {
                if (_type == null)
                {
                    if (!cachedTypes.ContainsKey(AssemblyQualifiedName))
                    {
                        cachedTypes.Add(AssemblyQualifiedName, Type.GetType(AssemblyQualifiedName));
                    }
                    _type = cachedTypes[AssemblyQualifiedName];
                    if (_type == null)
                    {
                        throw new InvalidDataException(string.Format("Cannot find type '{0}'.", AssemblyQualifiedName));
                    }
                }
                return _type;
            }
        }
        Type _type = null;

        private static Dictionary<string, Type> cachedTypes = new Dictionary<string, Type>();

        #endregion

        #region Characteristic Properties

        public bool IsLiquifiable
        {
            get
            {
                //We can take on serialising other types (e.g. [serializeable]) but special consideration must be made to each one (e.g. BinaryFormatter serializes backing fields!)
                return (DeclaresLiquifiable);// || (this.Type.IsSerializable && ParameterlessConstructor != null));
            }
        }

        public bool IsDumbSerializeable
        {
            get
            {
                return (this.Type.IsSerializable && !(typeof(ISerializable).IsAssignableFrom(this.Type)));
            }
        }

        public bool DeclaresLiquifiable
        {
            get
            {
                return this.Type.GetCustomAttributes(typeof(LiquifiableAttribute), false).Length > 0;
            }
        }

        public bool IsCollection
        {
            get
            {
                return (this.Type.IsArray || typeof(IList).IsAssignableFrom(this.Type));
            }
        }

        public ConstructorInfo ParameterlessConstructor
        {
            get
            {
                return this.Type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(x => x.GetParameters().Length == 0);
            }
        }

        public bool IsDelegate
        {
            get
            {
                if (isDelegate == null)
                {
                    isDelegate = typeof(Delegate).IsAssignableFrom(this.Type);
                }
                return isDelegate.Value;
            }
        }
        private Nullable<bool> isDelegate = null;


        public bool IsBlittable
        {
            get
            {
                if (!isBlittable.HasValue)
                {
                    isBlittable = IsTypeBlittable(this.Type);
                }
                return isBlittable.Value;
            }
        }
        private Nullable<bool> isBlittable = null;

        private bool IsTypeBlittable(Type type)
        {
            if (!type.IsValueType)
            {
                return false;
            }

            /* Here we test if a type is blittable or not. A type is blittable if it consists of only blittible members (basic data types like int, bool). This
             * is the most straightforward way to test for it, none of the properties, or combinations of thereof, of the Type are sufficient */

            try
            {
                object uo = FormatterServices.GetUninitializedObject(type);
                GCHandle H_array = GCHandle.Alloc(uo, GCHandleType.Pinned);
                H_array.Free();
            }
            catch (ArgumentException)
            {
                return false;
            }

            return true;
        }
        

        #endregion

        #region Utilities

        public object CreateInstance()
        {
            if (ParameterlessConstructor == null)
            {
                throw new Exception(string.Format("Cannot Instantiate {0} because it does not have a parameterless constructor.", AssemblyQualifiedName));
            }
            return ParameterlessConstructor.Invoke(null);
        }

        #endregion
    }

}
